/* 
 * File:   ipd_png.cpp
 * Author: dima
 * 
 * Created on 21 декабря 2014 г., 15:47
 */
#include "ipd_png.h"
#include "ipd_exception.h"

namespace ipd
{

ipd_png::ipd_png()
{
}

bool ipd_png::convert_png_to_img(color_model &type_img, png_uint_32 color_type)// convert type image from libpng to local format
{
    switch (color_type)
    {
    case PNG_COLOR_TYPE_GRAY:
    {
        type_img = tp_mono;
        break;
    }
    case PNG_COLOR_TYPE_RGB:
    {
        type_img = tp_RGB;
        break;

    }
    default:
    {
        return false;
    }
    }

    return true;
}

bool ipd_png::convert_img_to_png(png_uint_32 &color_type, color_model type_img)//convert type image from local format to libpng
{
    switch (type_img)
    {
    case tp_mono:
    {
        color_type = PNG_COLOR_TYPE_GRAY;
        break;
    }
    case tp_RGB:
    {
        color_type = PNG_COLOR_TYPE_RGB;
        break;
    }
    default:
    {
        return false;
    }
    }
    return true;
}

void ipd_png::load_png(ipd_img &img, std::string file_name) // load image
{
    png_structp png_ptr;
    png_infop info_ptr;
    // Buffer for header of PNG file (8 is the maximum size that can be checked)
    unsigned char header[4];

    // Open the file (don't forget 'b')
    FILE *fp = fopen(file_name.c_str(), "rb");
    if (fp == NULL)
        throw ipd_exception(std::string("File ") + file_name + std::string(" could not be opened for reading"), std::string("ipd_png"));

    // Read PNG file header
    fread(header, 1, 4, fp);

    // Check that file header match the PNG signature
    if (png_sig_cmp(png_const_bytep(header), 0, 4))
    {
        fclose(fp);
        throw ipd_exception(std::string("File ") + file_name + std::string(" is not recognized as a PNG file"), std::string("ipd_png"));
    }

    // Allocate and initialize a png_struct for reading PNG file
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr)
    {
        fclose(fp);
        throw ipd_exception(std::string("png_create_read_struct failed"), std::string("ipd_png"));
    }
    // Allocate and initialize png_info structure
    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
    {
        fclose(fp);
        throw ipd_exception(std::string("png_create_info_struct failed"), std::string("ipd_png"));
    }

    // Save calling environment for long jump
    if (setjmp(png_jmpbuf(png_ptr)))
    {
        fclose(fp);
        throw ipd_exception(std::string("Error during init_io"), std::string("ipd_png"));
    }
    // Initialize input/output for the PNG file
    png_init_io(png_ptr, fp);

    // number of bytes read from PNG file
    png_set_sig_bytes(png_ptr, 4);

    // Read the PNG image information
    png_read_info(png_ptr, info_ptr);

    // Get the image size for checking
    int height = png_get_image_height(png_ptr, info_ptr);
    int width = png_get_image_width(png_ptr, info_ptr);
    color_model type_img;
    png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);
    if (!convert_png_to_img(type_img, color_type))
    {
        fclose(fp);
        throw ipd_exception(std::string("Image model is not supported (Supports RGB and monochrome image)"), std::string("ipd_png"));
    }
    png_bytep * row_pointers;
    row_pointers = new png_bytep [height];
    for (int y = 0; y < height; y++)
        row_pointers[y] = new png_byte[png_get_rowbytes(png_ptr, info_ptr)];

    png_read_image(png_ptr, row_pointers);
    try
    {
        img.init(width, height, type_img);
    }
    catch (ipd::ipd_exception &except)
    {
        fclose(fp);
        for (int y = 0; y < height; y++)
            delete [](png_byte*) row_pointers[y];
        delete [](png_bytep*) row_pointers;
        throw ipd_exception(except.what(), except.in_file());
    }
    unsigned char* data = img.get_data();
    for (int y = 0; y < height; y++)
    {
        png_bytep row = row_pointers[y];
        for (int x = 0; x < width; x++)
        {
            switch (type_img)
            {
            case tp_mono:
            {
                png_bytep px = &(row[x]);
                data[y * width + x] = *px;
                break;
            }
            case tp_RGB:
            {
                png_bytep px = &(row[x * sz_RGB]);
                data[(y * width + x) * sz_RGB] = px[0];
                data[(y * width + x) * sz_RGB + 1] = px[1];
                data[(y * width + x) * sz_RGB + 2] = px[2];
                break;
            }
            }
        }
    }
    fclose(fp);
    for (int y = 0; y < height; y++)
        delete [](png_byte*) row_pointers[y];
    delete [](png_bytep*) row_pointers;
}

void ipd_png::save_png(ipd_img &img, std::string file_name, int bit_depth) // save image
{
    FILE *fp = fopen(file_name.c_str(), "wb");
    if (!fp)
        throw ipd_exception(std::string("File ") + file_name + std::string(" could not be opened for writing"), std::string("ipd_png"));

    png_structp png_ptr;
    png_infop info_ptr;

    /* initialize stuff */
    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr)
    {
        fclose(fp);
        throw ipd_exception(std::string("png_create_write_struct failed"), std::string("ipd_png"));
    }
    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
    {
        fclose(fp);
        throw ipd_exception(std::string("png_create_info_struct failed"), std::string("ipd_png"));
    }
    if (setjmp(png_jmpbuf(png_ptr)))
    {
        fclose(fp);
        throw ipd_exception(std::string("Error during init_io"), std::string("ipd_png"));
    }
    png_init_io(png_ptr, fp);

    /* write header */
    if (setjmp(png_jmpbuf(png_ptr)))
    {
        fclose(fp);
        throw ipd_exception(std::string("Error during writing header"), std::string("ipd_png"));
    }
    png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);
    color_model type_img = img.get_color_type();
    if (!convert_img_to_png(color_type, type_img))
    {
        fclose(fp);
        throw ipd_exception(std::string("Image model is not supported (Supports RGB and monochrome image)"), std::string("ipd_png"));
    }
    int height = img.get_height();
    int width = img.get_width();
    png_set_IHDR(png_ptr, info_ptr, width, height,
                 bit_depth, color_type, PNG_INTERLACE_NONE,
                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

    png_write_info(png_ptr, info_ptr);
    unsigned char* data = img.get_data();

    /* write bytes */
    if (setjmp(png_jmpbuf(png_ptr)))
    {
        fclose(fp);
        throw ipd_exception(std::string("Error during writing bytes"), std::string("ipd_png"));
    }
    png_bytep * row_pointers;
    row_pointers = new png_bytep [height];
    for (int y = 0; y < height; y++)
    {
        switch (type_img)
        {
        case tp_mono:
        {
            row_pointers[y] = new png_byte[width];
            for (int x = 0; x < width; x++)
                row_pointers[y][x] = data[y * width + x];
            break;
        }
        case tp_RGB:
        {
            row_pointers[y] = new png_byte[width * sz_RGB];
            unsigned int pos1, pos2;
            for (int x = 0; x < width; x++)
            {
                pos1 = x * sz_RGB;
                pos2 = (y * width + x) * sz_RGB;
                row_pointers[y][pos1] = data[pos2];
                row_pointers[y][pos1 + 1] = data[pos2 + 1];
                row_pointers[y][pos1 + 2] = data[pos2 + 2];
            }
            break;
        }
        }
    }
    png_write_image(png_ptr, row_pointers);


    /* end write */
    if (setjmp(png_jmpbuf(png_ptr)))
    {
        fclose(fp);
        throw ipd_exception(std::string("Error during end of write"), std::string("ipd_png"));
    }
    png_write_end(png_ptr, NULL);

    /* cleanup heap allocation */
    fclose(fp);
    for (int y = 0; y < height; y++)
        delete [](png_byte*) row_pointers[y];
    delete [](png_bytep*) row_pointers;
}

ipd_png::~ipd_png()
{

}

}
